/**
 * 
 */
package org.homeunix.thecave.plugins;

import java.io.*;
import java.text.*;
import java.util.*;
import javax.swing.JDialog;

import org.homeunix.thecave.buddi.plugin.api.model.*;
import org.homeunix.thecave.moss.swing.MossDocumentFrame;
import org.homeunix.thecave.buddi.plugin.api.util.TextFormatter;

import org.homeunix.thecave.buddi.plugin.api.BuddiImportPlugin;
import org.homeunix.thecave.buddi.plugin.api.exception.PluginMessage;
import org.homeunix.thecave.buddi.plugin.api.exception.PluginException;

import au.com.bytecode.opencsv.CSVReader;

/**
 * Implements the BuddiImportPlugin class to create a plugin for the
 * Buddi application to import comma separated value (CSV) data files.
 * 
 * @author Santthosh
 *
 */
public class CSV_Import extends BuddiImportPlugin {
	
	private ImportPanel importPanel;
	private JDialog importDialog;
	private File sourceFile,logFile;
	private static PrintWriter log;
	
	private String DATEFORMAT;
	
	private static MutableDocument mutableDocument;
	private static MutableModelFactory factory;	
	private static MutableTransaction transaction;	
	private static Map<String,Integer> fieldLayout; 
	
	private static java.util.Date date;
	private static String description;
	private static long amount;
	private static MutableSource from;
	private static MutableSource to;
		
	/*
	 * Constructor
	 */
	public CSV_Import()
	{
		
	}
	
	/**
	 * Responds to the Import button selection on the form and 
	 * performs the actual import of the selected transactions
	 * to a file specified by the user.
	 * 
	 * @param evt
	 */
	public void importActionPerformed(java.awt.event.ActionEvent evt)
	{
		/* See if we're importing a document exproted by Buddi */
		if(importPanel.standardImport.isSelected())						
			initStandardLayout();					
		
		/* We're importing a document from an external source */
		if(importPanel.manualImport.isSelected())
		{				
			try
			{
				/* Update the field mappings */
				checkFieldLocations();
			}
			catch (IllegalArgumentException e){						
				javax.swing.JOptionPane.showMessageDialog(importDialog,"Two fields cannot be at the same location","Invalid Selections!",javax.swing.JOptionPane.ERROR_MESSAGE);
				return;
			}   				
		}
		
		try
		{		
			/* set up logging using Decorator pattern */
			logFile = new File("import.log");			
			FileWriter logOutFile = new FileWriter(logFile);
			log = new PrintWriter(logOutFile);		
			log.println("Initiating import...");
			
			/* retrieve the date format string and set up the date formatter */
			DATEFORMAT = importPanel.dateFormatSelect.getSelectedItem().toString().trim();
			SimpleDateFormat dateFormat=new SimpleDateFormat(DATEFORMAT);			
			
			CSVReader reader = new CSVReader(new FileReader(sourceFile));
				
			/* skip over the header line if user requested to do so */
			if(importPanel.headersPresent.isSelected())
				reader.readNext();	
			
			/* read each line of the file, processing each as a new record */
			String [] nextLine;
			while ((nextLine = reader.readNext()) != null) 
			{
				try
				{				
					processRecord(nextLine,dateFormat);						
				}
				catch (Exception e){						
					log.println("Error occured while parsing contents : " + e.getMessage());
					log.println("Import utility will skip to the next record ...");
					continue;
				}   
			}	
			
			/* Show user that we were successful */
			javax.swing.JOptionPane.showMessageDialog(
					importDialog,"CSV Import to file " + sourceFile.getName() + " completed successfully. \n Please check import.log for record errors.", 
					"Import Succeeded!",javax.swing.JOptionPane.INFORMATION_MESSAGE);
		}
		catch (IOException e){
			/* file i/o error */
			e.printStackTrace();
			log.println(e.getMessage());
			javax.swing.JOptionPane.showMessageDialog(
					importDialog,"Import failed! \nReason: "  + e.getMessage() + ", further details in import.log file.", 
					"CSV Import Failed",javax.swing.JOptionPane.ERROR_MESSAGE);
		}   	
		catch (Exception e){
			/* any other type of error */
			e.printStackTrace();
			log.println(e.getMessage());
			javax.swing.JOptionPane.showMessageDialog(
					importDialog,"Import failed! \nReason: "  + e.getMessage() + ", further details in import.log file.", 
					"CSV Import Failed",javax.swing.JOptionPane.ERROR_MESSAGE);
		}   
		finally
		{	
			/* always clean up */
			log.flush(); 
			log.close();
			importDialog.dispose();	
		}		
	}	
	
	/**
	 * Responds to the Cancel button being pressed on the Import
	 * dialog. Cleans up and closes the dialog.
	 * 
	 * @param evt
	 */
	public void cancelActionPerformed(java.awt.event.ActionEvent evt)
	{
		importDialog.dispose();
	}

	/* (non-Javadoc)
	 * Implementation of the abstract method declared in the super class. This 
	 * method creates the Import dialog for the user to set up the options for
	 * importing a CSV file.
	 * 
	 * @see org.homeunix.thecave.buddi.plugin.api.BuddiImportPlugin#importData(org.homeunix.thecave.buddi.plugin.api.model.MutableDocument, org.homeunix.thecave.moss.swing.MossDocumentFrame, java.io.File)
	 */
	@Override
	public void importData(MutableDocument arg0, MossDocumentFrame arg1,
			File arg2) throws PluginException, PluginMessage {
		sourceFile = arg2;
		mutableDocument = arg0;		
		
		factory = new MutableModelFactory();
        importPanel = new ImportPanel();        
        
        fieldLayout = new HashMap<String,Integer>();
		        
        /* Register the event handler for the Import button */
        importPanel.importButton.addActionListener(new java.awt.event.ActionListener() {
        	public void actionPerformed(java.awt.event.ActionEvent evt) {
               importActionPerformed(evt);
        	}
        });
            
        /* Register the event handler for the Cancel button */
        importPanel.cancelButton.addActionListener(new java.awt.event.ActionListener() {
        	public void actionPerformed(java.awt.event.ActionEvent evt) {
                    cancelActionPerformed(evt);
                }
        });
        
        /* Load the task bar icon for the dialog */
        java.awt.Image image = new javax.swing.ImageIcon(ClassLoader.getSystemResource("img/BuddiFrameIcon.gif")).getImage();
                        
        /* create the import dialog embedded in the import panel */
        importDialog = new JDialog();
        importDialog.setModal(true);
        importDialog.setTitle("Import CSV file to Buddi accounts");                
        importDialog.setIconImage(image);
        importDialog.setLocationRelativeTo(null);        		        		
        importDialog.add(importPanel);        
        importDialog.pack();     
		
		importDialog.setVisible(true);
	}

	/* (non-Javadoc)
	 * Retrieves the name that will be displayed on the file
	 * menu for the import functionality to be invoked.
	 * 
	 * @see org.homeunix.thecave.moss.plugin.MossPlugin#getName()
	 */
	@Override
	public String getName() {
		return TextFormatter.getTranslation("CSV Import");	
	}
	
	/**
	 * Retrieve the field mappings set up by the user in the import dialog. Also,
	 * ensure that the user did not setup any redundant mappings.
	 * 
	 */
	public void checkFieldLocations() throws IllegalArgumentException
	{
		fieldLayout.clear();
		
		fieldLayout.put("date", Integer.parseInt(importPanel.dateSelect.getSelectedItem().toString()));
		
		/* Get field number mapped to the NUMBER field */
		int number = Integer.parseInt(importPanel.numberSelect.getSelectedItem().toString());
		if(fieldLayout.containsValue(number))
			throw new IllegalArgumentException("Redundant field mappings with number");
		fieldLayout.put("number", number);
		
		/* Get field number mapped to the DESCRIPTION field */
		int description = Integer.parseInt(importPanel.descriptionSelect.getSelectedItem().toString());
		if(fieldLayout.containsValue(description))
			throw new IllegalArgumentException("Redundant field mappings with description");
		fieldLayout.put("description", description);
		
		/* Get field number mapped to the AMOUNT field */
		int amount = Integer.parseInt(importPanel.amountSelect.getSelectedItem().toString());
		if(fieldLayout.containsValue(amount))
			throw new IllegalArgumentException("Redundant field mappings with amount");
		fieldLayout.put("amount", amount);
		
		/* Get field number mapped to the FROM field */
		int from = Integer.parseInt(importPanel.fromSelect.getSelectedItem().toString());
		if(fieldLayout.containsValue(from))
			throw new IllegalArgumentException("Redundant field mappings with from");		
		fieldLayout.put("from", from);
		
		/* Get field number mapped to the TO field */
		int to = Integer.parseInt(importPanel.toSelect.getSelectedItem().toString());
		if(fieldLayout.containsValue(to))
			throw new IllegalArgumentException("Redundant field mappings with to");
		fieldLayout.put("to", to);
		
		/* Get field number mapped to the MEMO field */
		int memo = Integer.parseInt(importPanel.memoSelect.getSelectedItem().toString());
		if(fieldLayout.containsValue(memo))
			throw new IllegalArgumentException("Redundant field mappings with memo");
		fieldLayout.put("memo", memo);
	}
	
	/**
	 * Function to initialize the standard layout map
	 * 
	 */
	public void initStandardLayout()
	{		
		fieldLayout.clear();
		
		fieldLayout.put("date", (Integer)0);
		fieldLayout.put("number", 1);
		fieldLayout.put("description", 2);
		fieldLayout.put("amount", 3);
		fieldLayout.put("from", 6);
		fieldLayout.put("to", 7);
		fieldLayout.put("memo", 8);
	}

	/**
	 * Function to parse the record and populate the transaction
	 */
	public static void processRecord(String[] nextLine, SimpleDateFormat dateFormat) throws Exception
	{														
		/* Parse the DATE and set it to the mutable transaction*/
		if(nextLine[fieldLayout.get("date")]==null ||
			nextLine[fieldLayout.get("date")].trim().isEmpty())
		{
			throw new ParseException("Date field is invalid",fieldLayout.get("date"));			
		}		
								
		/*Parse the DESCRIPTION and set it to mutable transaction*/		
		if(nextLine[fieldLayout.get("description")]==null ||
			nextLine[fieldLayout.get("description")].trim().isEmpty())
		{
			throw new ParseException("Description field is invalid",fieldLayout.get("description"));			
		}		
		
		/*Parse the AMOUNT and set it to mutable transaction*/		
		if(nextLine[fieldLayout.get("amount")]==null ||
			nextLine[fieldLayout.get("amount")].trim().isEmpty())
		{
			throw new ParseException("Number field is invalid",fieldLayout.get("amount"));			
		}
		
		float amountF = Float.parseFloat(nextLine[fieldLayout.get("amount")]);
		amount = (long) amountF * 100;				
		
		/*Parse the FROM and set it to mutable transaction*/
		if(nextLine[fieldLayout.get("from")]==null ||
			nextLine[fieldLayout.get("from")].trim().isEmpty())
		{
			throw new ParseException("From field is invalid",fieldLayout.get("From"));			
		}		
			
		/*Parse the TO and set it to mutable transaction*/
		if(nextLine[fieldLayout.get("to")]==null ||
			nextLine[fieldLayout.get("to")].trim().isEmpty())
		{
			throw new ParseException("To field is invalid",fieldLayout.get("to"));			
		}				
		
		date = dateFormat.parse(nextLine[fieldLayout.get("date")]);
		description = nextLine[fieldLayout.get("description")];
		from = mutableDocument.getAccount(nextLine[fieldLayout.get("from")]);		
		to = mutableDocument.getAccount(nextLine[fieldLayout.get("to")]);
		
		if(from==null)
		{			
			from = mutableDocument.getBudgetCategory(nextLine[fieldLayout.get("from")]);
		}
		if(to==null)
		{		
			to = mutableDocument.getBudgetCategory(nextLine[fieldLayout.get("to")]);
		}
				
		log.println("Assignments completed");
									
		transaction = factory.createMutableTransaction(date,description,amount,from,to);		

		/*Parse the MEMO and set it to mutable transaction, Memo is not essential*/
		if(nextLine[fieldLayout.get("memo")]==null &&
				nextLine[fieldLayout.get("memo")].trim().isEmpty());		
		else		
			transaction.setNumber(nextLine[fieldLayout.get("memo")]);				
								
		/*Parse the NUMBER and set it to mutable transaction, Number is not essential*/
		if(nextLine[fieldLayout.get("number")]==null &&
				nextLine[fieldLayout.get("number")].trim().isEmpty());		
		else		
			transaction.setNumber(nextLine[fieldLayout.get("number")]);
			
		mutableDocument.addTransaction(transaction);
		
		log.println("Added transaction to mutable document");
	}
}
